package com.zrkizzy.seckill.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.zrkizzy.seckill.config.mapper.JacksonObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis配置类
 *
 * @author zhangrongkang
 * @since  2023/3/21
 */
@Configuration
public class RedisConfig {

    /**
     * Redis序列化
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        JacksonObjectMapper jacksonObjectMapper = new JacksonObjectMapper();
        // 允许更改底层 VisibilityCheckers 的配置，以更改自动检测的属性类型的详细信息
        jacksonObjectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        jacksonObjectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        jacksonObjectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 通过StringRedisSerializer来序列化Key
        StringRedisSerializer keySerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(keySerializer);
        redisTemplate.setHashKeySerializer(keySerializer);

        // 通过GenericJackson2JsonRedisSerializer来序列化Value
        GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer(jacksonObjectMapper);
        redisTemplate.setValueSerializer(valueSerializer);
        redisTemplate.setHashValueSerializer(valueSerializer);

        // 设置连接工厂
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 确保redisTemplate被正确的初始化
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * 配置Redis获取库存Lua脚本
     * Redis性能受到网络波动的影响相对较大，因此需要使用Lua脚本来保证多个命令一次执行
     * <p>
     * 两种方式：
     * <p>
     * 第一种：
     * 1. 提前在Redis端写好Lua脚本<p>
     * 2. 在Java客户端去调用
     * <p>
     * 优点：需要调用时直接执行即可
     * <p>
     * 缺点：涉及到修改时不方便，并且后期有可能使用不到
     * <p>
     * 第二种：
     * <p>
     * 1. 在Java客户端写好Lua脚本<p>
     * 2. 将脚本发送到Redis上去执行
     * 优点：较为灵活
     * <p>
     * 缺点：每一次调用都需要提前发送到Redis客户端，造成网络通信时间延长<p>
     * @return Lua脚本
     */
    @Bean
    public DefaultRedisScript<Long> lockLuaScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        // lock.lua脚本位置和application.yml同级目录
        redisScript.setLocation(new ClassPathResource("lock.lua"));
        // 定义Lua脚本的返回值
        redisScript.setResultType(Long.class);
        return redisScript;
    }

}